/* Gimmick code for LOCKJAW, an implementation of the Soviet Mind Game

Copyright (C) 2006-2007 Damian Yerrick <tepples+lj@spamcop.net>

This work is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

Original game concept and design by Alexey Pajitnov.
The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
or The Tetris Company LLC.

*/

package fizzpicks.tetrapulp.tetrapulp;

import net.jscience.math.MathFP;

/**
 * Created by will
 * Date: May 3, 2008 5:13:13 AM
 */
public class Gimmicks extends PCSound {

/*
void initSpeed(LJField *p);  // in speed.c
void setSpeed(LJField *p, LJControl *c);  // in speed.c
int updLevelAfterPiece(LJField *p);  // in speed.c
int updLevelAfterLines(LJField *p, size_t nLines);  // in speed.c
*/

    /**
     * Applies gimmicks to a new game.
     *
     * @param p the playfield
     */
public static void initGimmicks(LJField p) {
  Speed.initSpeed(p);
  p.speed.entryDelay = 0;  /* new pieces will increase this */
  p.speed.lineDelay = 0;
  if (p.garbageStyle == LJGARBAGE.DRILL
      || p.gimmick == LJGM.DRILL) {
    p.garbageRandomness = 255;
    p.garbage = p.ceiling - 2;
  } else if (p.garbageStyle == LJGARBAGE.ZIGZAG) {
    setupZigzagField(p, p.ceiling * 3 / 4);
  }

}

    /**
     * Runs gimmicks for one frame of S.M.G.
     *
     * @param p the playfield
     * @param c the control settings
     * @return the rows that were modified
     */
public static LJBits gimmicks(LJField p, LJControl c) {
  LJBits changed = new LJBits();

  // In rhythm, lock the tetromino if the player
  // isn't keeping up with the rate
  if (p.speedState.curve == LJSPD.RHYTHM
      || p.speedState.curve == LJSPD.RHYTHMZERO) {
    p.bpmCounter += p.speedState.level;

    if (p.bpmCounter >= 0) {
      if (p.state == LJState.LANDED) {
        p.stateTime = 0;
      } else if (p.state == LJState.FALLING) {
        p.speed.gravity = MathFP.toFP(LJ_PF_HT);
      }
    }

    // In rhythm, increase BPM periodically
    p.speedupCounter += p.speedState.level;
    if(p.speedupCounter >= 60 * 60 * 64) {
      p.speedupCounter -= 60 * 60 * 64;
      p.speedState.level += 10;
      p.sounds.add(LJSND.SECTIONUP);
    }
  }

  // For each piece, set the entry and line delays.
  // Don't set it twice when spawning to replace the
  // first held piece (both LJSND_SPAWN and LJSND_HOLD
  // on the same piece).
  if (p.sounds.contains(LJSND.SPAWN)) /* | LJSND.LJSND_HOLD)) == LJSND.LJSND_SPAWN) */ {
    Speed.setSpeedNew(p, c);
  }

  if (p.sounds.contains(LJSND.LOCK)) {
    if (p.gimmick == LJGM.ITEMS) {
      if (p.nPieces >= 7) {
        p.canRotate = false;
        p.speed.gravity = MathFP.toFP(1);
      }
    }

    // Garbage in simulated multiplayer
    boolean simGarbage = p.nPieces >= 7
                     && p.garbageStyle.g >= LJGARBAGE._1.g
                     && p.garbageStyle.g <= LJGARBAGE._4.g
                     && (p.curPiece[1] == LJP.I
                         || ((p.pieceSet == LJRAND2.SZ
                              || p.pieceSet == LJRAND2.JLOSTZ)
                             && p.nPieces % 7 == 3));
    if (simGarbage) {
      p.garbage += p.garbageStyle.g;
    }

    // Banana attack in "Vs. with Items" gimmick
    if (p.gimmick == LJGM.ITEMS
        && (ljRand(p) & 0xF00) == 0xF00) {
      shuffleColumns(p);
      changed.add( (1 << LJ_PF_HT) - 1);
    }
  }
  return changed;
}

    /**
     * List of lines used for hotline scoring.
     * A line clear completed at row hotlineRows[i] is worth 100*(i + 1).
     */
    static final int[] hotlineRows = {
            0, 0, 0, 0, 1,
            0, 0, 0, 0, 2,
            0, 0, 0, 3, 0,
            0, 4, 0, 5, 6,
            0, 0, 0, 0
    };

    static final int garbageScoreTable[] = { 0, 0, 1, 2, 4 };
static final int tSpinGarbageScoreTable[] = { 0, 2, 4, 6, 6 };
static final int squareScoreTable[] =
 {1, 1, 1, 2, 3, 5, 8, 13,
  21, 34, 55, 89, 144, 200};

static final int tdsScoreTable[] = {0, 1, 3, 5, 8};
static final int tdsTSScoreTable[] = {4, 8, 12, 16};
static final int nesScoreTable[] = {0, 4, 10, 30, 120};


/**
 * Computes the score and outgoing garbage for lines
 * and adds them to the player's total.
 * @param p The playfield
 * @param lines Bit array where 1 means a line clear on this row.
 */
static void addLinesScore(LJField p, LJBits lines) {
  final int nLines = countOnes(lines);
  int oldLines = p.nLinesThisPiece;
  int tdsSection = p.lines / 10 + 1;

  p.lines += nLines;
  if (Speed.updLevelAfterLines(p, nLines)) {
    p.sounds.add(LJSND.SECTIONUP);
  }

  switch (p.scoreStyle) {

  case TNT64:
    if (nLines < 1) {
      return;
    }
    for (int i = nLines; i > 0; --i) {
      if (oldLines > squareScoreTable.length - 1) {
        oldLines = squareScoreTable.length - 1;
      }
      p.score += 100 * squareScoreTable[oldLines++];
    } break;

  case NES:
    {
      int garbageLevel = (nLines > 4) ? 4 : nLines;
      int value = nesScoreTable[garbageLevel];

      p.score += 10 * tdsSection * value;
      if (nLines >= 4) {
        p.sounds.add(LJSND.SETB2B);
      }
    } break;

  case TDS:
    {
      // Garbage based scoring system.
      int garbageLevel = (nLines > 4) ? 4 : nLines;
      boolean chain;
      int value;

      if (p.isSpin != IsSpin.NONE) {
        chain = (nLines >= 1);
        value = tdsTSScoreTable[garbageLevel];

        // TDS gives fewer points for a T-spin single
        // that involves a wall kick.
        if (p.isSpin == IsSpin.TWISTKICK && nLines < 2) {
          value >>= 2;
        }
      } else {
        chain = (nLines >= 4);
        value = tdsScoreTable[garbageLevel];
      }
      if (chain && p.chain && nLines >= 1) {  // b2b
        value = value * 3 / 2;
      }

      if (tdsSection > 20) {
        tdsSection = 20;
      }
      p.score += 100 * tdsSection * value;
      if (nLines >= 1) {
        if (chain) {
          p.sounds.add(LJSND.SETB2B);
          if (p.chain) {
            p.sounds.add(LJSND.B2B);
          }
        }
        p.chain = chain;
      }
    } break;

  case LJ:
  case LJ_NERF:
    if (nLines >= 1) {
      // Garbage based scoring system.
      int garbageLevel = (nLines > 4) ? 4 : nLines;
      boolean chain;
      int garbage;

      if (p.isSpin != IsSpin.NONE) {
        chain = (nLines >= 1);
        garbage = tSpinGarbageScoreTable[garbageLevel];
        if (p.scoreStyle == LJSCORE.LJ_NERF) {
          garbage /= 2;
        }
        garbage += (chain && p.chain) ? 1 : 0;
      } else {
        chain = (nLines >= 4);
        garbage = garbageScoreTable[garbageLevel]
                      + ((chain && p.chain) ? 1 : 0);
      }
      p.score += 100 * nLines + 200 * garbage;
      p.outGarbage += garbage;
      if (chain) {
        p.sounds.add(LJSND.SETB2B);
        if (p.chain) {
          p.sounds.add(LJSND.B2B);
        }
      }
      p.chain = chain;
    } break;

  case HOTLINE:
    for (int y = 0; y < 24; ++y) {
      if ((lines.bits & (1 << y)) != 0) {
        p.score += 100 * hotlineRows[y];
      }
    } break;

  }

  p.nLinesThisPiece += nLines;
}

/**
 * Puts a zigzag pattern of garbage into the playfield.
 * Useful as a test suite for calcZigzagGrade() in debrief.c.
 * @param p the playfield to set up
 * @param height the number of rows to set up
 */
static void setupZigzagField(LJField p, int height) {
  int hole = p.leftWall;
  int delta = 1;

  if (height > p.ceiling) {
    height = p.ceiling;
  }

  // set up example pattern
  for (int y = 0; y < height; ++y) {
    for (int x = p.leftWall; x < p.rightWall; ++x) {

      // A block should be in all cells of the row
      // except for the hole cell, which should be empty.
      if (x == hole) {
        p.b[y][x] = 0;
      } else {
        p.b[y][x] = 0x80;
      }
    }

    if (hole == p.rightWall - 1) {
      delta = -1;
    } else if (hole == p.leftWall) {
      delta = 1;
    }
    hole += delta;
  }

  // clear rows above pattern
  for (int y = height; y < LJ_PF_HT; ++y) {
    for (int x = p.leftWall; x < p.rightWall; ++x) {
      p.b[y][x] = 0;
    }
  }
}

}
